<?php
/**
 * [ActiveRecord] 模型查询类,构造出查询SQL
 *
 * @version $Id: Select.php 111 2013-09-06 06:08:38Z linsir123 $
 * @package ActiveRecord
 */
class ARSelect extends SqlBase
{
	/**
	 * 数据表替换
	 */
	const AS_TABLE = '`a`';

	/**
	 * 初始化查询结构,按`Select`语句进行拆分
	 *
	 * @var array
	 * @static
	 */
	private static $__partsInit = array(
		'DISTINCT' => false,
		'FIELD' => array(),
		'FROM' => array(),
		'WHERE' => null,
		'GROUP' => array(),
		'ORDER' => array(),
		'LIMIT' => array()
	);

	/**
	 * 数据表别名
	 *
	 * @var string
	 */
	public $asTable;

	/**
	 * 是否使用子查询
	 *
	 * @var bool
	 */
	public $fromSub = false;

	/**
	 * 当前查询的连接次数
	 *  // 构造完SQL后需要清空
	 *
	 * @var int
	 */
	public $joinTime = 0;

	/**
	 * 查询结构数组
	 *  // 表连接时,用于中转
	 *
	 * @var array
	 */
	public $joinPart;

	/**
	 * 查询结构数组
	 *
	 * @var array
	 */
	public $parts;

	/**
	 * `数据库表名`
	 *
	 * @var string
	 */
	public $table;

	/**
	 * 数据操作或查询使用的条件对象
	 *
	 * @var ARCondition
	 */
	public $oCondition;

	/**
	 * 构造函数
	 *
	 * @param string $table 需要查询的数据库表名
	 * @param ARCondition $oCondition 查询条件对象
	 */
	public function __construct($table, & $oCondition)
	{
		$this->setTable($table);

		///
		$this->oCondition = $oCondition;

		///
		$this->clean();
	}

	/**
	 * 返回对象对应的SQL语句
	 *
	 * @return string
	 */
	public function __toString()
	{
		$sql = 'SELECT';
		foreach ($this->parts as $key => $value) {
			$method = '_to' . ucfirst($key);
			if (method_exists($this, $method)) {
				$string = call_user_func_array(array($this, $method), array($value));
				empty($string) or $sql .= ' ' . $string;
			}
		}

		///
		$this->clean();

		return $sql;
	}

	/**
	 * 检测某个键是否设置
	 *
	 * @param string $key 键名
	 *
	 * @return bool
	 */
	public function hadSet($key)
	{
		return ! empty($this->parts[$key]);
	}

	/**
	 * 设置`SELECT`数组的某一个键的内容(覆盖)
	 *
	 * @param string $key 键名
	 * @param mixed $value 内容
	 */
	public function set($key, $value)
	{
		$this->parts[$key] = $value;
	}

	/**
	 * 设置`SELECT`数组的某一个键的内容(累加)
	 *
	 * @param string $key 键名
	 * @param mixed $value 内容
	 */
	public function push($key, $value)
	{
		array_push($this->parts[$key], $value);
	}

	/**
	 * 重新设置操作的数据表名
	 *
	 * @param string $table 需要查询的数据库表名
	 */
	public function setTable($table)
	{
		$this->table = $this->_quote($table);
	}

	/**
	 * 重置查询对象
	 * // 1、初妈化查询数组结构
	 * // 2、清空条件对象
	 */
	public function clean()
	{
		$this->joinTime = 0;
		$this->joinPart = array();
		$this->parts = self::$__partsInit;
		$this->asTable = null;
		$this->fromSub = false;

		///
		$this->oCondition->clean();
	}

	/**
	 * 表连接时,将数据进行合并
	 *
	 * @param ARSelect $oSelect 被连接的对象
	 * @param string $type 连接类型
	 * @param string $key 主键名
	 * @param string $fKey 外键名
	 */
	public function join($oSelect, $type, $key, $fKey)
	{
		list($asTableThis, $asTable) = $this->_getJoinAsList($oSelect);
		$this->push('FROM', " {$type} {$oSelect->table} AS {$asTable} ON
			{$asTableThis}.`{$key}`={$asTable}.`{$fKey}` ");

		///
		$this->oCondition->join($oSelect->oCondition, $asTable);

		foreach (array('FIELD', 'ORDER') as $k)
			$this->joinPart[$k][] = array($oSelect->parts[$k], $asTable);

		///
		if ($oSelect->joinTime > 0) {
			$tmp = $oSelect->parts['FROM'];
			foreach ($tmp as $v)
				$this->push('FROM', $v);

			///
			foreach ($oSelect->joinPart as $k => $v) {
				if (empty($this->joinPart[$k]))
					$this->joinPart[$k] = $v;
				else
					$this->joinPart[$k] = array_merge($this->joinPart[$k], $v);
			}
		}

		///
		$oSelect->clean();
	}

	/**
	 * 构造子查询
	 *
	 * @param ARSelect $oSelect 查询对象
	 */
	public function fromSub($oSelect)
	{
		$from = $oSelect->__toString();

		///
		$this->fromSub = true;
		$this->asTable = '`b`';
		$this->push('FROM', "({$from}) AS {$this->asTable}");
	}

	/**
	 * 取得默认的数据表别名
	 */
	private function _getAsTable($default = null)
	{
		if (! empty($default))
			return $default;

		///
		return empty($this->asTable) ? self::AS_TABLE : $this->asTable;
	}

	/**
	 * 取得当前的数据表别名列表
	 */
	private function _getJoinAsList($oSelect)
	{
		static $list = array('`b`', '`c`', '`d`');

		if ($oSelect->joinTime > 0) {
			$this->joinTime = $oSelect->joinTime;
			$this->asTable = $this->oCondition->asTable = $list[$this->joinTime ++];
			$asTable = self::AS_TABLE;
		}
		else
			$asTable = $list[$this->joinTime ++];

		///
		return array($this->_getAsTable(), $asTable);
	}

	/**
	 * 构造查询条件
	 */
	private function _toWhere()
	{
		///
		return $this->oCondition->__toString();
	}

	/**
	 * 构造重复设置
	 */
	private function _toDistinct($data)
	{
		///
		return (! $data) ? '' : 'DISTINCT';
	}

	/**
	 * 构造查询表
	 */
	private function _toFrom($data)
	{
		if (! $this->fromSub) {
			$from = "{$this->table} AS " . $this->_getAsTable();
			array_unshift($data, $from);
		}

		///
		return 'FROM ' . implode(' ', $data);
	}

	/**
	 * 构造查询区间
	 */
	private function _toLimit($data)
	{
		if (empty($data))
			return '';

		///
		list($start, $offset) = $data;

		///
		return "LIMIT {$start},{$offset}";
	}

	/**
	 * 构造查询字段
	 *
	 * 数据结构如：
	 * @example array(
	'`talbe`.id',
	'username,password',
	'position as p,count(`id`) as `count`',
	)
	 */
	private function _toField($data)
	{
		$data = $this->_formatField($data);
		if (is_array($this->joinPart['FIELD'])) {
			foreach ($this->joinPart['FIELD'] as $v) {
				list($tmp, $asTable) = $v;

				///
				$tmp = $this->_formatField($tmp, $asTable);
				$data = array_merge($data, $tmp);
			}
		}

		///
		return implode(', ', $data);
	}

	/**
	 * 构造查询排序
	 *
	 * 数据结构如：
	 * @example array(
	array('id', 3),
	array('id', 0),
	array('`table`.`id` desc', 2),
	)
	 */
	private function _toOrder($data)
	{
		$list = $indexes = array();
		foreach ($data as $v) {
			$v[] = null;
			$list[] = $v;
			$indexes[] = $v[1];
		}
		if (is_array($this->joinPart['ORDER'])) {
			foreach ($this->joinPart['ORDER'] as $data) {
				list($data, $asTable) = $data;
				foreach ($data as $v) {
					$v[] = $asTable;
					$list[] = $v;
					$indexes[] = $v[1];
				}
			}
		}

		///
		$data = array();
		array_multisort($indexes, SORT_DESC, $list);
		foreach ($list as $v) {
			list($field, , $preTable, $asTable) = $v;
			$tmp = $this->_formatOrder($field, $asTable, $preTable);
			if (! empty($tmp))
				$data[] = $tmp;
		}

		///
		if (empty($data))
			return '';

		///
		return "ORDER BY " . implode(',', $data);
	}

	/**
	 * 构造查询聚合
	 *
	 * 数据结构如：
	 * @example "`table`.`id`"
	 * @example "`table`.id"
	 * @example "table.`id`"
	 * @example "`id`"
	 * @example "id, name"
	 */
	private function _toGroup($data)
	{
		if (empty($data))
			return '';

		///
		list($field, $preTable) = $data;
		$data = $this->_formatGroup($field, null, $preTable);

		///
		return "GROUP BY " . $data;
	}

	/**
	 * FIELD,解析
	 *
	 * @param array $data
	 * @param string $asTable
	 *
	 * @return array
	 */
	private function _formatField($data, $asTable = null)
	{
		///
		$asTable = $this->_getAsTable($asTable);

		///
		if (empty($data))
			return array($asTable . '.*');

		///
		$columns = array();
		foreach ($data as $v) {
			///
			if ($this->_isExpression($v)) {
				$columns[] = $v;
				continue;
			}

			///
			$fields = explode(',', $v);
			foreach ($fields as $field) {
				if (preg_match("/^(`?(\w+)`?\.)?`?(\w+)`?( AS `?(\w+)`?)?$/i",
					trim($field),
					$m)
				) {
					$v = $asTable . '.' . $this->_quote($m[3]);
					if (isset($m[5]) && ! empty($m[5]))
						$v .= ' AS ' . $this->_quote($m[5]);

					$columns[] = $v;
				}
			}
		}

		///
		return $columns;
	}

	/**
	 * ORDER,解析
	 *
	 * @param string $data
	 * @param string $asTable
	 * @param bool $preTable
	 *
	 * @return string
	 */
	private function _formatOrder($data, $asTable = null, $preTable = true)
	{
		///
		if ($this->_isExpression($data))
			return $data;

		///
		if ($preTable)
			$asTable = $this->_getAsTable($asTable) . '.';
		else
			$asTable = '';

		///
		$columns = array();
		$fields = explode(',', $data);
		foreach ($fields as $field) {
			if (preg_match("/^(`?(\w+)`?\.)?`?(\w+)`?( (asc|desc))?$/i",
				trim($field),
				$m)
			) {
				$tmp = $asTable .
					$this->_quote($m[3]) .
					' ' . (isset($m[5]) ? strtoupper($m[5]) : "ASC");

				///
				$columns[] = $tmp;
			}
		}

		///
		return implode(',', $columns);
	}

	/**
	 * GROUP,解析
	 *
	 * @param string $data
	 * @param string $asTable
	 * @param bool $preTable
	 *
	 * @return string
	 */
	private function _formatGroup($data, $asTable = null, $preTable = true)
	{
		///
		if ($this->_isExpression($data))
			return $data;

		///
		if ($preTable)
			$asTable = $this->_getAsTable($asTable) . '.';
		else
			$asTable = '';

		///
		$columns = array();
		$fields = explode(',', $data);
		foreach ($fields as $field) {
			if (preg_match("/^(`?(\w+)`?\.)?`?(\w+)`?( (asc|desc))?$/i",
				trim($field),
				$m)
			) {
				$tmp = $asTable .
					$this->_quote($m[3]) .
					(isset($m[5]) ? (' ' . strtoupper($m[5])) : '');

				///
				$columns[] = $tmp;
			}
		}

		///
		return implode(',', $columns);
	}

	/**
	 * 验证所给的字符串中是否包含表达式
	 *
	 * @param string $string
	 *
	 * @return bool
	 */
	private function _isExpression($string)
	{
		return strpos($string, '(') !== false;
	}
}
